package appClientModule;

import java.util.ArrayList;
import java.util.List;

public class ReinforcementLearningAI implements ShitHeadAI {

	double alpha;
	double discount;
	double greedyProb;
	ReinforcementFeatureSet featureSet = new ReinforcementFeatureSet();
	double featureWeights[] = new double[featureSet.NUMFEATURES];

	public ReinforcementLearningAI(double alpha, double discount,
			double greedyProbability) {
		this.alpha = alpha; // 0.01; // step size
		this.discount = discount; // 0.99;
		this.greedyProb = greedyProbability;
	}

	public double getGreedyProb() {
		return greedyProb;
	}

	public void setGreedyProb(double greedyProb) {
		this.greedyProb = greedyProb;
	}

	/**
	 * This gets the q-values from the feature values. It returns the dot
	 * product of the feature values and the feature weights.
	 */
	double qvalue(double[] featureVals) {
		double tot = 0;
		for (int i = 0; i < featureSet.NUMFEATURES; i++)
			// featureWeights are all 0 but work correctly in training
			tot += featureWeights[i] * featureVals[i];
		return tot;
	}

	/**
	 * resets the weights.
	 * 
	 * @param initVal
	 *            the initial value given by a box in the GUI
	 */
	public void doreset(double initVal) {
		for (int i = 0; i < featureSet.NUMFEATURES; i++)
			featureWeights[i] = initVal;
	}

	double[] prevFeatureVals = new double[featureSet.NUMFEATURES];
	double prevReward = 0;

	@Override
	public String basicMove(Hand hand, Card[] pile, int pileLength) {
		updateParameters(hand, pile, pileLength, null);
		// determining top card
		Card top;
		if (pileLength == 0) {
			return playBestMove(hand, pile, pileLength, null);
		} else {
			top = pile[0];
			return playBestMove(hand, pile, pileLength, top);
		}
	}

	private void updateParameters(Hand hand, Card[] pile, int pileLength, Card startCard) {
		// Update Parameters
		double[] featureVals = featureSet.featureValues(hand, pile, pileLength,
				startCard);
		double delta = prevReward + discount * qvalue(featureVals) - qvalue(prevFeatureVals);
		for (int i = 0; i < featureSet.NUMFEATURES; i++) {
			featureWeights[i] = featureWeights[i] + alpha * delta * prevFeatureVals[i];
		}
		// Save feature values
		prevFeatureVals = featureVals;

		// this needs to save the reward in the environment?
		//prevReward=0;
	}

	private String playBestMove(Hand hand, Card[] pile, int pileLength, Card top) {
		String command = "error";
		double rand = Math.random() * 100;
		if (rand < greedyProb) { // act greedily
			if (hand.length() > 1) { // there are cards in the hand
				command = playHand(hand, pile, pileLength, top);
				return command;
			} else if (hand.isFaceUp()) {
				command = playFaceUp(hand, pile, pileLength, top);
				return command;
			} else {
				return facedown(hand, top);
			}
		} else { //act randomly
			if (hand.length() > 1) { // there are cards in the hand
				command = playRandom(hand, pile, pileLength, top);
				return command;
			} else if (hand.isFaceUp()) {
				command = playRandomUp(hand, pile, pileLength, top);
				return command;
			} else {
				return facedown(hand, top);
			}
		}
	}

	/*
	 * This is the method to play the cards when there are cards in the hand.
	 */
	private String playHand(Hand hand, Card[] pile, int pileLength, Card top) {
		List<Card> list = new ArrayList<Card>();
		Card bestCard = null;
		double bestVal;
		int duplicates = 0;
		if (top != null) {
			for (int i = 0; i < hand.length() - 1; i++) {
				Card startCard = hand.getCard(i);
				if (startCard != null) {
					if (startCard.getValue() >= top.getValue()
							|| startCard.getValue() == 10
							|| startCard.getValue() == 2) {
						list.add(startCard);
					}
				}
			}
			if (list.isEmpty()) {
				prevReward = -pileLength;
				return ("turn:pickup:");
			}
			
			// determining which card to play - choose initial index of card to
			// play
			bestCard = list.get(0);
			bestVal = qvalue(featureSet.featureValues(hand, pile, pileLength, bestCard));
			for (int i = 1; i < list.size(); i++) {
				Card card = list.get(i);
				double cardVal = qvalue(featureSet.featureValues(hand, pile, pileLength, card));
				if (cardVal > bestVal) {
					bestCard = card;
					bestVal = cardVal;
				}
			}
			
			for (Card c : list) {
				if (c.getValue() == bestCard.getValue()) {
					duplicates++;
				}
			}
		}// end top
		else {
			for (int i = 0; i < hand.length() - 1; i++) {
				Card startCard = hand.getCard(i);
				if (startCard != null) {
					list.add(startCard);
				}
			}
			
			// determining which card to play - choose initial index of card to
			// play
			bestCard = list.get(0);
			bestVal = qvalue(featureSet.featureValues(hand, pile, pileLength, bestCard));
			for (int i = 1; i < list.size(); i++) {
				Card card = list.get(i);
				double cardVal = qvalue(featureSet.featureValues(hand, pile, pileLength, card));
				if (cardVal > bestVal) {
					bestCard = card;
					bestVal = cardVal;
				}
			}
			
			for (Card c : list) {
				if (c.getValue() == bestCard.getValue()) {
					duplicates++;
				}
			}
		}
		
		if (bestCard != null) {// if the card is playable, play it
			updateParameters(hand, pile, pileLength, bestCard);
			return commandcreator("turn:", hand, bestCard.getValue(), duplicates);
		}
		
		return ("turn:pickup:");
	}

	/*
	 * This is the method to play the face up cards.
	 */
	private String playFaceUp(Hand hand, Card[] pile, int pileLength, Card top) {
		List<Card> list = new ArrayList<Card>();
		Card bestCard = null;
		double bestVal;
		int count = 0;
		if (top != null) {
			for (int i = 0; i < 3; i++) {
				Card startCard = hand.getFaceUp(i);
				if (startCard != null) {
					if (startCard.getValue() >= top.getValue()
							|| startCard.getValue() == 10
							|| startCard.getValue() == 2) {
						list.add(startCard);
					}
				}
			}
			if (list.isEmpty()) {
				prevReward = -pileLength;
				return ("turn:pickup:");
			}
			
			// determining which card to play - choose initial index of card to
			// play
			bestCard = list.get(0);
			bestVal = qvalue(featureSet.featureValues(hand, pile, pileLength, bestCard));
			for (int i = 1; i < list.size(); i++) {
				Card card = list.get(i);
				double cardVal = qvalue(featureSet.featureValues(hand, pile, pileLength, card));
				if (cardVal > bestVal) {
					bestCard = card;
					bestVal = cardVal;
				}
			}
			
			for (Card c : list) {
				if (c.getValue() == bestCard.getValue()) {
					count++;
				}
			}
		}// end top
		else {
			for (int i = 0; i < 3; i++) {
				Card startCard = hand.getFaceUp(i);
				if (startCard != null) {
					list.add(startCard);
				}
			}
			
			// determining which card to play - choose initial index of card to
			// play
			bestCard = list.get(0);
			bestVal = qvalue(featureSet.featureValues(hand, pile, pileLength, bestCard));
			for (int i = 1; i < list.size(); i++) {
				Card card = list.get(i);
				double cardVal = qvalue(featureSet.featureValues(hand, pile, pileLength, card));
				if (cardVal > bestVal) {
					bestCard = card;
					bestVal = cardVal;
				}
			}
			
			for (Card c : list) {
				if (c.getValue() == bestCard.getValue()) {
					count++;
				}
			}

		}
		if (bestCard != null) {// if the card is playable, play it
			updateParameters(hand, pile, pileLength, bestCard);
			return commandcreatorFU("turn:faceup:", hand, bestCard.getValue(),count);
		}
		return ("turn:pickup:");
	}

	/*
	 * This is the method to play a random card when there are cards in the
	 * hand.
	 */
	private String playRandom(Hand hand, Card[] pile, int pileLength, Card top) {
		List<Card> list = new ArrayList<Card>();
		Card bestCard = null;
		int count = 0;
		if (top != null) {
			for (int i = 0; i < hand.length() - 1; i++) {
				Card startCard = hand.getCard(i);
				if (startCard != null) {
					if (startCard.getValue() >= top.getValue()
							|| startCard.getValue() == 10
							|| startCard.getValue() == 2) {
						list.add(startCard);
					}
				}
			}
			if (list.isEmpty()) {
				prevReward = -pileLength;
				return ("turn:pickup:");
			}

			// determining which card to play - choose initial index of card to
			// play
			int randomIndex = (int) (Math.random() * list.size());
			Card startCard = list.get(randomIndex);
			bestCard = startCard;
			for (Card c : list) {
				if (c.getValue() == bestCard.getValue()) {
					count++;
				}
			}
		}// end top
		else {
			for (int i = 0; i < hand.length() - 1; i++) {
				Card startCard = hand.getCard(i);
				if (startCard != null) {
					list.add(startCard);
				}
			}
			// determining which card to play - choose initial index of card to
			// play
			int randomIndex = (int) Math.random() * list.size();
			Card startCard = list.get(randomIndex);
			bestCard = startCard;
			for (Card c : list) {
				if (c.getValue() == bestCard.getValue()) {
					count++;
				}
			}

		}
		if (bestCard != null) {// if the card is playable, play it
			updateParameters(hand, pile, pileLength, bestCard);
			return commandcreator("turn:", hand, bestCard.getValue(), count);
		}
		return ("turn:pickup:");
	}

	/*
	 * This is the method to play a random card to play the face up cards.
	 */
	private String playRandomUp(Hand hand, Card[] pile, int pileLength, Card top) {
		List<Card> list = new ArrayList<Card>();
		Card bestCard = null;
		int count = 0;
		if (top != null) {
			for (int i = 0; i < 3; i++) {
				Card startCard = hand.getFaceUp(i);
				if (startCard != null) {
					if (startCard.getValue() >= top.getValue()
							|| startCard.getValue() == 10
							|| startCard.getValue() == 2) {
						list.add(startCard);
					}
				}
			}
			if (list.isEmpty()) {
				prevReward = -pileLength;
				return ("turn:pickup:");
			}
			// determining which card to play - choose initial index of card to play
			int randomIndex = (int) Math.random() * list.size();
			Card startCard = list.get(randomIndex);
			bestCard = startCard;
			for (Card c : list) {
				if (c.getValue() == bestCard.getValue()) {
					count++;
				}
			}
		}// end top
		else {
			for (int i = 0; i < 3; i++) {
				Card startCard = hand.getFaceUp(i);
				if (startCard != null) {
					list.add(startCard);
				}
			}
			// determining which card to play - choose initial index of card to
			// play
			int randomIndex = (int) Math.random() * list.size();
			Card startCard = list.get(randomIndex);
			bestCard = startCard;
			for (Card c : list) {
				if (c.getValue() == bestCard.getValue()) {
					count++;
				}
			}

		}
		if (bestCard != null) {// if the card is playable, play it
			updateParameters(hand, pile, pileLength, bestCard);
			return commandcreatorFU("turn:faceup:", hand, bestCard.getValue(),count);
		}
		return ("turn:pickup:");
	}

	public String commandcreator(String command, Hand hand, int value,
			int number) {
		if (number == 0)
			return (command.concat("pickup:"));
		if (number > 1)
			command = command.concat("multi:" + number + ":");
		int addedcount = 0;
		for (int i = 0; i < hand.length() - 1; i++)
			if (hand.getCard(i).getValue() == value) {
				addedcount++;
				command = command.concat(hand.getCard(i).getNumber() + ":");
				if (addedcount == number)
					break;
			}
		return command;
	}

	// FU for Face Up
	public String commandcreatorFU(String command, Hand hand, int value,
			int number) {
		if (number == 0)
			return (command.concat("pickup:"));
		if (number > 1)
			command = command.concat("multi:" + number + ":");
		int addedcount = 0;
		for (int i = 0; i < 3; i++)
			if (hand.getFaceUp(i) != null)
				if (hand.getFaceUp(i).getValue() == value) {
					addedcount++;
					command = command.concat(hand.getFaceUp(i).getNumber()
							+ ":");
					if (addedcount == number)
						break;
				}
		return command;
	}

	public String facedown(Hand hand, Card card) {
		String command;
		for (int n = 0; n < 3; n++)
			if (hand.getFaceDown(n) != null) {
				if (card == null) {
					command = "turn:facedown:"
							+ hand.getFaceDown(n).getNumber() + ":";
					return command;
				} else if (hand.getFaceDown(n).getValue() == 2
						|| hand.getFaceDown(n).getValue() == 10) {
					command = "turn:facedown:"
							+ hand.getFaceDown(n).getNumber() + ":";
					return command;
				} else if (hand.getFaceDown(n).getValue() >= card.getValue()) {
					command = "turn:facedown:"
							+ hand.getFaceDown(n).getNumber() + ":";
					return command;
				} else {
					command = "turn:facedown:pickup:"
							+ hand.getFaceDown(n).getNumber() + ":";
					return command;
				}

			}
		return "error";
	}

}